package web.dao;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author orlin
 *
 */
public class Criteria extends QueryPatternImpl {

	public static final String FROM = "from";
	public static final String TO   = "to";

	public enum SqlOperator {EQUAL, LESS, GREAT, IN, BETWEEN, ISNULL, ISNOTNULL, ASC, DESC, LIKE, AND, OR};

	private SqlOperator sqlOperator;
	protected Object key;
	protected Object value;

	public static Criteria equal(String fieldName, Object value) {
		return new Criteria(SqlOperator.EQUAL, fieldName, value);
	}

	public static Criteria isNull(String fieldName) {
		return new Criteria(SqlOperator.ISNULL, fieldName, null);
	}

	public static Criteria isNotNull(String fieldName) {
		return new Criteria(SqlOperator.ISNOTNULL, fieldName, null);
	}

	public static Criteria lessThan(String fieldName, Object value) {
		return new Criteria(SqlOperator.LESS, fieldName, value);
	}

	public static Criteria greatThan(String fieldName, Object value) {
		return new Criteria(SqlOperator.GREAT, fieldName, value);
	}

	public static Criteria like(String fieldName, Object value) {
		return new Criteria(SqlOperator.LIKE, fieldName, value);
	}

	public static Criteria inList(String fieldName, Collection<?> values) {
		return new Criteria(SqlOperator.IN, fieldName, values);
	}

	public static Criteria between(String fieldName, Object fromValue, Object toValue) {
		return new Criteria(SqlOperator.BETWEEN, fieldName, new Object[] {fromValue, toValue});
	}

	public static Criteria and(Criteria value1, Criteria value2) {
		return new Criteria(SqlOperator.AND, value1, value2);
	}

	public static Criteria or(Criteria value1, Criteria value2) {
		return new Criteria(SqlOperator.OR, value1, value2);
	}

	protected Criteria() {
		super();
	}

	protected Criteria(SqlOperator sqlOperator, Object key, Object value) {
		this.sqlOperator = sqlOperator;
		this.key = key;
		this.value = value;
	}

	public SqlOperator getSqlOperator() {
		return sqlOperator;
	}

	public void setSqlOperator(SqlOperator sqlOperator) {
		this.sqlOperator = sqlOperator;
	}

	@SuppressWarnings("unchecked")
	public <V> V getValue() {
		return (V) value;
	}

	public void setValue(Object value) {
		this.value = value;
	}

	@SuppressWarnings("unchecked")
	public <K> K getKey() {
		return (K) key;
	}

	public void setKey(Object key) {
		this.key = key;
	}

	public String[] getFieldNames() {
		switch (sqlOperator) {
			case IN:
				Collection<String> list = new ArrayList<String>();
				for (int i = 0; i < ((Collection<?>)getValue()).size(); i++) { // or for (int i = 0; i < ((Object[])getValue()).length; i++) {
					list.add(getKey().toString() + i);
				}
				return list.toArray(new String[list.size()]);
			case BETWEEN:
				return new String[] {FROM + getKey(), TO + getKey()};
			default:
				return new String[] {getKey()};
		}
	}

	@Override
	public String getWhereClause() {
		StringBuffer result = new StringBuffer();
		// TODO fix dbu
		//result.append(getParentQuery().getNickName());
		//result.append(DOT);
		if (getKey() instanceof Criteria) {
			Criteria sub = getKey();
			result.append(sub.getWhereClause());
		} else {
			result.append(getKey());
		}
		switch (sqlOperator) {
			case EQUAL:
				result.append(EQUAL);
				result.append(COLUMN);
				result.append(getKey());
				break;
			case LESS:
				result.append(LESS);
				result.append(COLUMN);
				result.append(getKey());
				break;
			case GREAT:
				result.append(GREAT);
				result.append(COLUMN);
				result.append(getKey());
				break;
			case LIKE:
				result.append(LIKE);
				result.append(COLUMN);
				result.append(getKey());
				break;
			case IN:
				result.append(IN);
				result.append(LBRAKET);
				for (Iterator<String> i = Arrays.asList(getFieldNames()).iterator();i.hasNext();) {
					result.append(COLUMN);
					result.append(i.next());
					if (i.hasNext()) {
						result.append(COMMA);
					}
				}
				result.append(RBRAKET);
				break;
			case BETWEEN:
				result.append(BETWEEN);
				result.append(COLUMN);
				result.append(getFieldNames()[0]);
				result.append(AND);
				result.append(COLUMN);
				result.append(getFieldNames()[1]);
				break;
			case ISNULL:
				result.append(IS);
				result.append(NULL);
				break;
			case ISNOTNULL:
				result.append(IS);
				result.append(NOT);
				result.append(NULL);
				break;
			case AND:
				result.append(AND);
				Criteria and = getValue();
				result.append(and.getWhereClause());
				break;
			case OR:
				result.append(OR);
				Criteria or = getValue();
				result.append(or.getWhereClause());
				break;
			default:
				throw new RuntimeException("unknown sql operator");
		}
		String whereClause = super.getWhereClause();
		if ((null != whereClause) && (!whereClause.isEmpty())) {
			result.append(AND);
			result.append(whereClause);
		}
		return result.toString();
	}

	@Override
	public Map<String, Object> getParameters() {
		Map<String, Object> parameters = new HashMap<String, Object>();
		if (this.getClass().getSuperclass().equals(QueryPatternImpl.class)) {
			/** this should NOT be run by successor class */
			if ((getSqlOperator() == SqlOperator.IN) || 
				(getSqlOperator() == SqlOperator.BETWEEN)) {
				String[] fieldNames = (String[]) getFieldNames();
				Object[] parameter = (Object[])getValue();
				for (int i = 0; i < fieldNames.length; i++) {
					parameters.put(fieldNames[i], parameter[i]);
				}
			} else if ((getSqlOperator() == SqlOperator.AND) || (getSqlOperator() == SqlOperator.OR)) {
				Criteria sub = getKey();
				parameters.putAll(sub.getParameters());
				sub = getValue();
				parameters.putAll(sub.getParameters());
			} else if ((getSqlOperator() != SqlOperator.ISNULL) && (getSqlOperator() != SqlOperator.ISNOTNULL)) {
				parameters.put((String) getKey(), getValue());
			}
		}

		Map<String, Object> subParameters = super.getParameters();
		if (null != subParameters) {
			parameters.putAll(subParameters);
		}
		return parameters;
	}
}
